package com.month.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.month.train.business.domain.SkToken;
import com.month.train.business.domain.SkTokenExample;
import com.month.train.business.enums.RedisKeyPreEnum;
import com.month.train.business.mapper.SkTokenMapper;
import com.month.train.business.mapper.cust.SkTokenMapperCust;
import com.month.train.business.req.SkTokenQueryReq;
import com.month.train.business.req.SkTokenSaveReq;
import com.month.train.business.resp.SkTokenQueryResp;
import com.month.train.common.exception.BusinessException;
import com.month.train.common.exception.BusinessExceptionEnum;
import com.month.train.common.resp.PageResp;
import com.month.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SkTokenService {

    private static final Logger LOG = LoggerFactory.getLogger(SkTokenService.class);
    @Resource
    private SkTokenMapper skTokenMapper;

    @Resource
    private SkTokenMapperCust skTokenMapperCust;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private DailyTrainSeatService dailyTrainSeatService;

    @Resource
    private DailyTrainStationService dailyTrainStationService;

    @Value("${spring.profiles.active}")
    private String env;

    /**
     * 初始化
     * */
    public void genDaily(Date date, String trainCode) {
        LOG.info("删除日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
        skTokenMapper.deleteByExample(skTokenExample);

        DateTime now = DateTime.now();
        SkToken skToken = new SkToken();
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);
        skToken.setId(SnowUtil.getSnowflakeNextId());
        skToken.setCreateTime(now);
        skToken.setUpdateTime(now);

        int seatCount = dailyTrainSeatService.countSeat(date, trainCode);
        LOG.info("车次【{}】座位数：{}", trainCode, seatCount);

        long stationCount = dailyTrainStationService.countByTrainCode(date, trainCode);
        LOG.info("车次【{}】到站数：{}", trainCode, stationCount);

        // 3/4需要根据实际卖票比例来定，一趟火车最多可以卖（seatCount * stationCount）张火车票
        int count = (int) (seatCount * stationCount); // * 3/4);
        LOG.info("车次【{}】初始生成令牌数：{}", trainCode, count);
        skToken.setCount(count);

        skTokenMapper.insert(skToken);
    }

    public void save(SkTokenSaveReq req) {
        DateTime now = DateTime.now();
        SkToken skToken = BeanUtil.copyProperties(req, SkToken.class);
        if(ObjectUtil.isNull(skToken.getId())) {
            skToken.setId(SnowUtil.getSnowflakeNextId());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        }else {
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }

    // 查询列表
    public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req) {
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.setOrderByClause("id desc");
        SkTokenExample.Criteria criteria = skTokenExample.createCriteria();

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getPageSize());

        // 分页
        PageHelper.startPage(req.getPage(), req.getPageSize());
        List<SkToken> skTokenList= skTokenMapper.selectByExample(skTokenExample);

        PageInfo<SkToken> pageInfo = new PageInfo<>(skTokenList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<SkTokenQueryResp> list = BeanUtil.copyToList(skTokenList, SkTokenQueryResp.class);
        PageResp<SkTokenQueryResp>PageResp = new PageResp<SkTokenQueryResp>();
        PageResp.setTotal(pageInfo.getTotal());
        PageResp.setList(list);
        return PageResp;
    }

    public void delete(Long id) {
        skTokenMapper.deleteByPrimaryKey(id);
    }

    /*获取令牌*/
    public boolean validSkToken(Date date, String trainCode, Long memberId) {
        LOG.info("会员【{}】获取日期【{}】车次【{}】的令牌开始",memberId, DateUtil.formatDate(date), trainCode);

        // 先获取令牌锁，再校验令牌余量，防止机器人抢票，lockKey是令牌
        String lockKey = RedisKeyPreEnum.SK_TOKEN + "-" + DateUtil.formatDate(date) + "-" + trainCode + "-" + memberId;

//        Boolean setIfAbsent = redissonClient.opsForValue().setIfAbsent(lockKey, lockKey, 5, TimeUnit.SECONDS);
//        if(Boolean.TRUE.equals(setIfAbsent)) {
//            LOG.info("获取令牌锁成功! lockKey:{}", lockKey);
//        }else {
//            LOG.info("获取令牌锁失败! lockKey:{}", lockKey);
//            return false;
//        }

        RLock lock = null;
        try {
            if(!"dev".equals(env)) {
                lock = redissonClient.getLock(lockKey);
                boolean tryLock = lock.tryLock(0, 5,TimeUnit.SECONDS);
                if(tryLock) {
                    LOG.info("获取令牌锁成功!");

                    // 令牌约等于库存，令牌没有了，就不在卖票了
//                int updateCount = skTokenMapperCust.decrease(date, trainCode);
//                if(updateCount > 0) {
//                    return true;
//                }else {
//                    return false;
//                }
                }else {
                    LOG.info("获取令牌锁失败!");
                    throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_REPEAT);
                }
            }

            String skTokenCountKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + DateUtil.formatDate(date) + "-" + trainCode;
            RBucket<Object> bucket = redissonClient.getBucket(skTokenCountKey);
            Object skTokenCount = bucket.get();

            if (skTokenCount != null) {
                LOG.info("缓存中有该车次令牌大闸的key：{}", skTokenCountKey);
                int count =  (int)skTokenCount - 1;
                if (count < 0) {
                    LOG.error("获取令牌失败：{}", skTokenCountKey);
                    return false;
                } else {
                    LOG.info("获取令牌后，令牌余数：{}", count);
                    bucket.set(count, 60, TimeUnit.SECONDS);
                    if (count % 5 == 0) {
                        skTokenMapperCust.decrease(date, trainCode, 5);
                    }
                    return true;
                }
            } else {
                LOG.info("缓存中没有该车次令牌大闸的key：{}", skTokenCountKey);
                SkTokenExample skTokenExample = new SkTokenExample();
                skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
                List<SkToken> tokenCountList = skTokenMapper.selectByExample(skTokenExample);
                if (tokenCountList.isEmpty()) {
                    LOG.info("找不到日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
                    return false;
                }

                SkToken skToken = tokenCountList.get(0);
                if (skToken.getCount() <= 0) {
                    LOG.info("日期【{}】车次【{}】的令牌余量为0", DateUtil.formatDate(date), trainCode);
                    return false;
                }

                Integer count = skToken.getCount() - 1;
                skToken.setCount(count);
                LOG.info("将该车次令牌大闸放入缓存中，key: {}， count: {}", skTokenCountKey, count);
                bucket.set(count, 60, TimeUnit.SECONDS);
                return true;
            }
        } catch (InterruptedException e) {
            LOG.error("购票异常", e);
        }finally {
            if(!"dev".equals(env) && lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return false;
    }
}
